package net.midnighttd.engine.entity;

import net.midnighttd.engine.view.sprite.*;
import net.midnighttd.engine.*;
import net.midnighttd.engine.entity.Pathfinder.Pathfinder;

import java.util.*;
import java.util.List;
import java.awt.*;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;

/**
 * Author: Adam Scarr
 * Date: 19/04/2009
 * Time: 6:48:22 PM
 */
public class EntityManager {
    private Map<String, Entity> entities = new HashMap<String, Entity>();
    private SpriteManager sm;
    private EntityMap activeEnts = new EntityMap();
    private Engine game;
    private LinkedList<Point> directions = new LinkedList<Point>();
    private List<EntityExistanceListener> entityExistanceListeners = new LinkedList<EntityExistanceListener>();
    private Pathfinder pathfinder;
    private Map<Point, TriggerListener> triggerListeners = new HashMap<Point, TriggerListener>();
    private LinkedList<CollisionStateListener> collisionStateListeners = new LinkedList<CollisionStateListener>();
    private Map<String, EntityFactory> entityFactories = new HashMap<String, EntityFactory>();

    public EntityManager(Engine game) {
        this.game = game;
        sm = game.getSpriteManager();
        pathfinder = new Pathfinder(game);
        initDirections();
    }

    /**
     * Imports a set of entities from an xml file.
     */
    public void loadFromXML(Element e) {
        NodeList spritesets = e.getElementsByTagName("spriteset");
        for(int i = 0; i < spritesets.getLength(); i++) {
            Element spriteset = (Element)spritesets.item(i);
            sm.loadFromXML(ResourceManager.getXML(spriteset.getAttribute("src")));
        }

        for(String entityType: entityFactories.keySet()) {
            EntityFactory factory = entityFactories.get(entityType);
            NodeList entities = e.getElementsByTagName(entityType);

            for(int i = 0; i < entities.getLength(); i++) {
                Entity ent = factory.createEntity(game, sm, entityType, (Element)entities.item(i));

                if(this.entities.containsKey(ent.getName())) {
                    throw new Error("Entity " + ent.getName() + " overlaps with existing entity");
                }

                this.entities.put(ent.getName(), ent);
            }
        }
    }

    public void addEntityFactory(String entityType, EntityFactory factory) {
        entityFactories.put(entityType, factory);
    }

    public void addTriggerListener(int x, int y, TriggerListener t) {
        triggerListeners.put(new Point(x, y), t);
    }

    public void initDirections() {
        directions.add(new Point(-1, 0));
        directions.add(new Point( 1, 0));
        directions.add(new Point( 0,-1));
        directions.add(new Point( 0, 1));
    }

    /**
     * Gets a copy of a named Entity.
     * @param entname    The name of the entity
     * @return              The entity identified by spritename.
     */
    public Entity createEntity(String entname) {
        //System.out.println("Created a new " + entname);
        Entity ent = entities.get(entname);
        if(ent == null) throw new Error("Could not find ent " + entname);
        Entity duplicate = ent.getDuplicate();
       // add(duplicate);

        notifyEntityCreated(duplicate);

        return duplicate;
    }

    public Entity getEntity(String entname) {
        return entities.get(entname);
    }

    /**
     * Gets a list of entitys currently at the given coordinates
     * @param x X coordinate
     * @param y Y coordinate
     * @return List of entities
     */
    public EntityList getActiveEntsAt(int x, int y) {
        return activeEnts.get(x,y);
    }

    public Collection<Entity> getActiveEnts() {
        return activeEnts.getAll();
    }

    public void addActiveEnt(Entity e) {
        activeEnts.add(e);

        notifyEntityCreated(e);
        if(!e.isPassable()) notifyCollisionStateChange();
    }

    public void removeActiveEnt(Entity e, int x, int y) {
        activeEnts.remove(e, x, y);

        notifyEntityDeleted(e);
        sm.remove(e.getSprite());
        if(!e.isPassable()) notifyCollisionStateChange();
    }

    public void removeActiveEnt(Entity e) {
        removeActiveEnt(e, e.getX(), e.getY());
    }

    public void updateActiveEnt(Entity e, int oldX, int oldY) {
        activeEnts.move(e, oldX, oldY);
        TriggerListener t = triggerListeners.get(new Point(e.getX(), e.getY()));
        if(t != null) {
            t.trigger(e);
        }

        if(!e.isPassable()) notifyCollisionStateChange();
    }

    public void purgeActiveEnts() {
        activeEnts.clear();
    }

    public boolean isPassable(int x, int y) {
        for(Entity e: activeEnts.get(x, y)) {
            if(!e.isPassable()) return false;
        }
        
        return true;
    }

    /**
     * Returns a list of entitys sorted by their distance from the given entity, up to a maximum distance of dist.
     *
     * This is pretty quick, all active entities are stored spatially. Its just a matter of a breadth first search.
     * @param entity
     * @param dist
     * @return
     */
    public LinkedList<Entity> getEntitiesByDistanceTo(Entity entity, double dist) {
        HashSet<Point> visited = new HashSet<Point>();
        LinkedList<Entity> entsByDistance = new LinkedList<Entity>();
        Queue<Point> searchQueue = new LinkedList<Point>();
        Point start = new Point(entity.getX(), entity.getY());

        searchQueue.add(start);
        Point p;
        while((p = searchQueue.poll()) != null && p.distance(start) < dist) {
            visited.add(p);

            List<Entity> activeEnts = getActiveEntsAt(p.x, p.y);
            if(activeEnts != null) {
                for(Entity e: activeEnts) {
                    if(e != entity && e.isActive()) {
                        entsByDistance.add(e);
                    }
                }
            }

            for(Point direction: directions) {
                Point next = new Point(p.x + direction.x, p.y + direction.y);
                if(!visited.contains(next) && !searchQueue.contains(next)) {
                    searchQueue.add(next);
                }
            }
        }

        return entsByDistance;
    }

    public List<Entity> getEntsOfType(String s) {
        List<Entity> ents = new LinkedList<Entity>();
        for(Entity e: entities.values()) {
            if(e.getType().equals(s)) {
                ents.add(e);
            }
        }

        return ents;
    }

    public Pathfinder getPathfinder() {
        return pathfinder;
    }

    public void addEntityExistanceListener(EntityExistanceListener l) {
        entityExistanceListeners.add(l);
    }

    public void notifyEntityCreated(Entity e) {
        for(EntityExistanceListener udl: entityExistanceListeners) {
            udl.entityCreated(e);
        }
    }

    public void notifyEntityDeleted(Entity e) {
        for(EntityExistanceListener udl: entityExistanceListeners) {
            udl.entityDeleted(e);
        }
    }

     public void notifyCollisionStateChange() {
        System.out.println("Collision state change!");
        for(CollisionStateListener c: new LinkedList<CollisionStateListener>(collisionStateListeners)) {
            c.CollisionStateChanged();
        }
    }

    public void addCollisionStateListener(CollisionStateListener collisionStateListener) {
        collisionStateListeners.add(collisionStateListener);
    }

    public Entity createEntityAt(int x, int y, String ent) {
        Entity e = createEntity(ent);
        e.setActive(true);
        e.setLocation(x, y);

        return e;
    }
}
